﻿// The Nova Project by Ken Beckett.
// Copyright (C) 2007-2012 Inevitable Software, all rights reserved.
// Released under the Common Development and Distribution License, CDDL-1.0: http://opensource.org/licenses/cddl1.php

using Nova.Parsing;
using Nova.Rendering;

namespace Nova.CodeDOM
{
    /// <summary>
    /// Represents a symbolic reference that hasn't been resolved to a direct reference (includes references that
    /// are unresolved because they are ambiguous, or because the type arguments or method arguments don't match
    /// in number or type).
    /// </summary>
    public class UnresolvedRef : TypeRefBase
    {
        #region /* CONSTRUCTORS */

        /// <summary>
        /// Create an <see cref="UnresolvedRef"/>.
        /// </summary>
        public UnresolvedRef(string name, bool isFirstOnLine)
            : base(name, isFirstOnLine)
        { }

        /// <summary>
        /// Create an <see cref="UnresolvedRef"/>.
        /// </summary>
        public UnresolvedRef(string name)
            : base(name, false)
        { }

        /// <summary>
        /// Create an <see cref="UnresolvedRef"/>.
        /// </summary>
        public UnresolvedRef(string name, int lineNumber, int column)
            : this(name, false)
        {
            _lineNumber = lineNumber;
            _columnNumber = (ushort)column;
        }

        /// <summary>
        /// Create an <see cref="UnresolvedRef"/>.
        /// </summary>
        public UnresolvedRef(SymbolicRef symbolicRef)
            : this(symbolicRef.Name, symbolicRef.IsFirstOnLine)
        {
            Parent = symbolicRef.Parent;
            SetLineCol(symbolicRef);
        }

        /// <summary>
        /// Create an <see cref="UnresolvedRef"/>.
        /// </summary>
        public UnresolvedRef(TypeRefBase typeRefBase, bool copyTypeArguments)
            : this(typeRefBase.Name, typeRefBase.IsFirstOnLine)
        {
            Parent = typeRefBase.Parent;
            SetLineCol(typeRefBase);
            if (copyTypeArguments)
                _typeArguments = typeRefBase.TypeArguments;
            _arrayRanks = typeRefBase.ArrayRanks;
        }

        /// <summary>
        /// Create an <see cref="UnresolvedRef"/>.
        /// </summary>
        protected internal UnresolvedRef(Token token)
            : this(token.NonVerbatimText, token.IsFirstOnLine)
        {
            NewLines = token.NewLines;
            SetLineCol(token);
        }

        /// <summary>
        /// Create an <see cref="UnresolvedRef"/>.
        /// </summary>
        protected internal UnresolvedRef(string name, Token token)
            : this(token)
        {
            // Initialize with token first, but then override to the specified name
            _reference = name;
        }

        #endregion

        #region /* PROPERTIES */

        /// <summary>
        /// The name of the <see cref="UnresolvedRef"/>.
        /// </summary>
        public override string Name
        {
            get { return (string)_reference; }
        }

        /// <summary>
        /// The descriptive category of the <see cref="SymbolicRef"/>.
        /// </summary>
        public override string Category
        {
            get { return "unresolved"; }
        }

        /// <summary>
        /// True if this <see cref="UnresolvedRef"/> is the target of an assignment.
        /// </summary>
        public bool IsTargetOfAssignment
        {
            get
            {
                // Handle "expression.UnresolvedRef = ..."
                if (_parent is Dot && _parent.Parent is Assignment && ((Assignment)_parent.Parent).Left == _parent && ((Dot)_parent).Right == this)
                    return true;
                // Handle "expression[...] = ..." (where the hidden IndexerRef is the UnresolvedRef
                if (_parent is Index && _parent.Parent is Assignment && ((Assignment)_parent.Parent).Left == _parent && _parent.HiddenRef == this)
                    return true;
                // Handle "UnresolvedRef = ..."
                if (_parent is Assignment && ((Assignment)_parent).Left == this)
                {
                    // Don't treat as the target of an assignment in the special case of an "embedded collection" initialization.
                    // This is identified by the both the parent AND the right side of the assignment being an Initializer.  In such
                    // a case, the property being assigned should be a collection, and the getter will be used instead of the setter
                    // and the items in the Initializer will be added to the existing collection.
                    Assignment assignment = (Assignment)_parent;
                    return !(assignment.Parent is Initializer && assignment.Right is Initializer);
                }
                return false;
            }
        }

        /// <summary>
        /// Returns true if the UnresolvedRef represents an explicit interface implementation.
        /// </summary>
        public bool IsExplicitInterfaceImplementation
        {
            get
            {
                if (Parent is Dot)
                {
                    if (Parent.Parent is MethodDeclBase)
                        return (((MethodDeclBase)Parent.Parent).ExplicitInterfaceExpression == Parent);
                    if (Parent.Parent is PropertyDeclBase)
                        return (((PropertyDeclBase)Parent.Parent).ExplicitInterfaceExpression == Parent);
                }
                return false;
            }
        }

        #endregion

        #region /* STATIC METHODS */

        /// <summary>
        /// Create an <see cref="UnresolvedRef"/> from the specified <see cref="Token"/>.
        /// </summary>
        public static UnresolvedRef Create(Token identifier)
        {
            return (identifier != null ? new UnresolvedRef(identifier) : null);
        }

        #endregion

        #region /* METHODS */

        /// <summary>
        /// Always <c>true</c>.
        /// </summary>
        public override bool IsPossibleDelegateType
        {
            get { return true; }
        }

        /// <summary>
        /// Determine if the current reference refers to the same code object as the specified reference.
        /// </summary>
        public override bool IsSameRef(SymbolicRef symbolicRef)
        {
            UnresolvedRef unresolvedRef = (symbolicRef is AliasRef ? ((AliasRef)symbolicRef).Alias.Expression.SkipPrefixes() : symbolicRef) as UnresolvedRef;
            if (unresolvedRef == null || (string)Reference != (string)unresolvedRef.Reference)
                return false;

            // The strings of the UnresolvedRefs match, but we have to also verify that any Dot prefixes
            // match - if either side has one, they must match, otherwise neither side can have one.
            Dot parentDot = _parent as Dot;
            Dot parentDot2 = symbolicRef.Parent as Dot;
            SymbolicRef dotPrefix = (parentDot != null && parentDot.Right == this ? parentDot.Left as SymbolicRef : null);
            SymbolicRef dotPrefix2 = (parentDot2 != null && parentDot2.Right == this ? parentDot2.Left as SymbolicRef : null);
            return (dotPrefix == null || dotPrefix2 == null || dotPrefix.IsSameRef(dotPrefix2));
        }

        /// <summary>
        /// Calculate a hash code for the referenced object which is the same for all references where IsSameRef() is true.
        /// </summary>
        public override int GetIsSameRefHashCode()
        {
            // Make the hash codes as unique as possible while still ensuring that they are identical
            // for any objects for which IsSameRef() returns true.
            int hashCode = base.GetIsSameRefHashCode();
            if (_parent is Dot && ((Dot)_parent).Right == this && ((Dot)_parent).Left is SymbolicRef)
                hashCode ^= ((SymbolicRef)((Dot)_parent).Left).GetIsSameRefHashCode();
            return hashCode;
        }

        /// <summary>
        /// Determine if the specified TypeRefBase refers to the same generic type, regardless of actual type arguments.
        /// </summary>
        public override bool IsSameGenericType(TypeRefBase typeRefBase)
        {
            return (typeRefBase is UnresolvedRef && (_typeArguments != null ? _typeArguments.Count : 0) == typeRefBase.TypeArgumentCount && Name == typeRefBase.Name);
        }

        /// <summary>
        /// Dispose the <see cref="UnresolvedRef"/>.
        /// </summary>
        public override void Dispose()
        {
            _parent = null;
        }

        /// <summary>
        /// Get the full name of the object, including the namespace name.
        /// </summary>
        public override string GetFullName()
        {
            return Reference as string;
        }

        #endregion

        #region /* PARSING */

        internal static new void AddParsePoints()
        {
            // Parse generic type and method references and arrays here in UnresolvedRef, because they may
            // or may not parse as resolved.  Built-in types and '?' nullable types are parsed in TypeRef,
            // because they will parse as resolved.

            // Use a parse-priority of 100 (IndexerDecl uses 0, Index uses 200, Attribute uses 300)
            Parser.AddParsePoint(ParseTokenArrayStart, 100, ParseArrayRanks);

            // Use a parse-priority of 100 (GenericMethodDecl uses 0, LessThan uses 200)
            Parser.AddParsePoint(ParseTokenArgumentStart, 100, ParseTypeArguments);
            // Support alternate symbols for doc comments:
            // Use a parse-priority of 100 (GenericMethodDecl uses 0, PropertyDeclBase uses 200, BlockDecl uses 300, Initializer uses 400)
            Parser.AddParsePoint(ParseTokenAltArgumentStart, 100, ParseAltTypeArguments);
        }

        /// <summary>
        /// Parse array ranks.
        /// </summary>
        public static Expression ParseArrayRanks(Parser parser, CodeObject parent, ParseFlags flags)
        {
            // Verify that we seem to match an array rank pattern
            // (otherwise abort so it can be parsed as an array index or Attribute)
            if (parser.HasUnusedIdentifier && PeekArrayRanks(parser))
                return new UnresolvedRef(parser, parent, true, false);
            return null;
        }

        /// <summary>
        /// Parse type arguments.
        /// </summary>
        public static Expression ParseTypeArguments(Parser parser, CodeObject parent, ParseFlags flags)
        {
            // Verify that we seem to match a type argument list pattern
            // (otherwise abort so that the LessThan operator will get a chance to parse it)
            if (parser.HasUnusedIdentifier && PeekTypeArguments(parser, ParseTokenArgumentEnd, flags))
                return new UnresolvedRef(parser, parent, false, true);
            return null;
        }

        /// <summary>
        /// Parse type arguments using the alternate delimiters.
        /// </summary>
        public static Expression ParseAltTypeArguments(Parser parser, CodeObject parent, ParseFlags flags)
        {
            // Verify that we seem to match a type argument list pattern
            // (otherwise abort so that PropertyDeclBase will get a chance to parse it)
            // Only supported inside documentation comments - subroutines will look for the
            // appropriate delimiters according to the parser state.
            if (parser.InDocComment && parser.HasUnusedIdentifier && PeekTypeArguments(parser, ParseTokenAltArgumentEnd, flags))
                return new UnresolvedRef(parser, parent, false, true);
            return null;
        }

        /// <summary>
        /// Construct an unresolved reference to an array type, or generic type.
        /// </summary>
        protected UnresolvedRef(Parser parser, CodeObject parent, bool isArray, bool isGeneric)
            : base(parser, parent)
        {
            Token token = parser.RemoveLastUnusedToken();
            _reference = token.NonVerbatimText;  // Get the type name
            NewLines = token.NewLines;
            SetLineCol(token);
            
            if (isArray)
            {
                MoveCommentsAsPost(token);   // Get any comments after the identifier
                ParseArrayRanks(parser);     // Parse the array ranks
            }
            else if (isGeneric)
            {
                MoveCommentsAsPost(token);   // Get any comments after the identifier
                _typeArguments = ParseTypeArgumentList(parser, this);  // Parse the type arguments

                // Check for array ranks on the generic type
                if (parser.TokenText == ParseTokenArrayStart && PeekArrayRanks(parser))
                    ParseArrayRanks(parser);
            }
        }

        #endregion

        #region /* RENDERING */

        public override void AsTextExpression(CodeWriter writer, RenderFlags flags)
        {
            UpdateLineCol(writer, flags);
            writer.Write((string)_reference);
            AsTextTypeArguments(writer, _typeArguments, flags);
            AsTextArrayRanks(writer, flags);
        }

        #endregion
    }
}
